const SHA1_H0:u32 = 0x67452301;
const SHA1_H1:u32 = 0xefcdab89;
const SHA1_H2:u32 = 0x98badcfe;
const SHA1_H3:u32 = 0x10325476;
const SHA1_H4:u32 = 0xc3d2e1f0;

const ISAL_SHA1_DIGEST_NWORDS:usize = 5;
const ISAL_SHA1_BLOCK_SIZE:usize = 64;

pub struct Sha1Ctx {
    total_length:usize,
    partial_block_buffer_length:usize,
    partial_block_buffer:[u8;ISAL_SHA1_BLOCK_SIZE * 2], 
    digest:[u32;ISAL_SHA1_DIGEST_NWORDS],
}

impl Sha1Ctx {
    pub fn new() -> Self {
        Self { 
            total_length:0, 
            partial_block_buffer_length:0,
            partial_block_buffer:[0;ISAL_SHA1_BLOCK_SIZE * 2], 
            digest:[SHA1_H0, SHA1_H1, SHA1_H2, SHA1_H3, SHA1_H4],
        }
    }

    pub fn update(&mut self, data:&[u8]) {
        let mut input_data = data;
        // Advance byte counter
        self.total_length += data.len();

        // If there is anything currently buffered in the extra blocks, append to it until it
        // contains a whole block. Or if the user's buffer contains less than a whole block, append
        // as much as possible to the extra block.
        if (self.partial_block_buffer_length != 0) || (input_data.len() < ISAL_SHA1_BLOCK_SIZE) {
            // Compute how many bytes to copy from user buffer into extra block
            let copy_len = std::cmp::min(ISAL_SHA1_BLOCK_SIZE - self.partial_block_buffer_length, input_data.len());
            let partial_block_buffer_copy_end = self.partial_block_buffer_length + copy_len;

            if copy_len != 0 {
                self.partial_block_buffer[self.partial_block_buffer_length..partial_block_buffer_copy_end].copy_from_slice(&input_data[0..copy_len]);
                self.partial_block_buffer_length += copy_len;
                input_data = &input_data[copy_len..];
            }
            // The extra block should never contain more than 1 block here
            assert!(self.partial_block_buffer_length <= ISAL_SHA1_BLOCK_SIZE);

            // If the extra block buffer contains exactly 1 block, it can be hashed.
            if self.partial_block_buffer_length >= ISAL_SHA1_BLOCK_SIZE {
                    self.partial_block_buffer_length = 0;
                    Self::sha1_single(&self.partial_block_buffer, &mut self.digest);
            }
        }

        // If the extra blocks are empty, begin hashing what remains in the user's buffer.
        while input_data.len() >= ISAL_SHA1_BLOCK_SIZE {
            Self::sha1_single(input_data, &mut self.digest);
            input_data = &input_data[ISAL_SHA1_BLOCK_SIZE..];
        }

        if input_data.len() > 0 {
            let copy_len = input_data.len();
            self.partial_block_buffer[0..copy_len].copy_from_slice(input_data);
            self.partial_block_buffer_length = input_data.len();
        }
        return;
    }

    pub fn finalize(&mut self, digest:&mut [u8;std::mem::size_of::<u32>() * ISAL_SHA1_DIGEST_NWORDS]) {
        // Padding the first block
        self.partial_block_buffer[self.partial_block_buffer_length] = 0x80;
        self.partial_block_buffer_length += 1;

        self.partial_block_buffer[self.partial_block_buffer_length..ISAL_SHA1_BLOCK_SIZE].fill(0);

        // Calculate the first block without total_length if padding needs 2 block
        if self.partial_block_buffer_length > ISAL_SHA1_BLOCK_SIZE - 8 {
            Self::sha1_single(&mut self.partial_block_buffer, &mut self.digest);
            // Padding the second block
            self.partial_block_buffer[0..ISAL_SHA1_BLOCK_SIZE].fill(0);
        }

        // Padding the block
        let len_in_bit:[u8;std::mem::size_of::<u64>()] = (self.total_length as u64 * 8).to_be_bytes();
        self.partial_block_buffer[ISAL_SHA1_BLOCK_SIZE - 8..ISAL_SHA1_BLOCK_SIZE].copy_from_slice(&len_in_bit);
        Self::sha1_single(&self.partial_block_buffer, &mut self.digest);

        /* Output the digests of mh_sha1 */
        seq!(N in 0..=4 { digest[N*std::mem::size_of::<u32>()..(N+1)*std::mem::size_of::<u32>()].copy_from_slice(&self.digest[N].to_be_bytes()); });
    }

    pub(crate) fn sha1_single(data:&[u8], digest:&mut [u32;ISAL_SHA1_DIGEST_NWORDS]) {
        let mut w = [0u32;16];
        macro_rules! W  { ($x:expr) => { w[$x & 15] };}
        macro_rules! F1 { ($b:expr, $c:expr, $d:expr) => { ($d ^ ($b & ($c ^ $d)))};}
        macro_rules! F2 { ($b:expr, $c:expr, $d:expr) => { ($b ^ $c ^ $d)};}
        macro_rules! F3 { ($b:expr, $c:expr, $d:expr) => { (($b & $c) | ($d & ($b | $c)))};}
        macro_rules! F4 { ($b:expr, $c:expr, $d:expr) => { ($b ^ $c ^ $d) };}
        macro_rules! rol32 { ($x:expr,$shift:expr) => { $x.rotate_left($shift) };}
        macro_rules! step00_19 {
            ($i:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr) => {                                                               
                if $i > 15  { W!($i) = rol32!(W!($i - 3) ^ W!($i - 8) ^ W!($i - 14) ^ W!($i - 16), 1);  }                   
                else { W!($i) = u32::from_be_bytes(data[$i*std::mem::size_of::<u32>()..($i+1)*std::mem::size_of::<u32>()].try_into().unwrap()); }                                                          
                $e = $e.wrapping_add(rol32!($a, 5)).wrapping_add(F1!($b, $c, $d)).wrapping_add(0x5a827999).wrapping_add(W!($i));                                        
                $b = rol32!($b, 30);
            };
        }
        macro_rules! step20_39 {
            ($i:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr) => {                                                               
                W!($i) = rol32!(W!($i - 3) ^ W!($i - 8) ^ W!($i - 14) ^ W!($i - 16), 1);                              
                $e = $e.wrapping_add(rol32!($a, 5)).wrapping_add(F2!($b, $c, $d)).wrapping_add(0x6ed9eba1).wrapping_add(W!($i));                                        
                $b = rol32!($b, 30)
            };
        }
        macro_rules! step40_59 {
            ($i:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr) => {                                                               
                W!($i) = rol32!(W!($i - 3) ^ W!($i - 8) ^ W!($i - 14) ^ W!($i - 16), 1);                              
                $e = $e.wrapping_add(rol32!($a, 5)).wrapping_add(F3!($b, $c, $d)).wrapping_add(0x8F1bbcdc).wrapping_add(W!($i));                                        
                $b = rol32!($b, 30);
            };
        }
        macro_rules! step60_79 {
            ($i:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr) => {                                                               
                W!($i) = rol32!(W!($i - 3) ^ W!($i - 8) ^ W!($i - 14) ^ W!($i - 16), 1);                              
                $e = $e.wrapping_add(rol32!($a, 5)).wrapping_add(F4!($b, $c, $d)).wrapping_add(0xca62c1d6).wrapping_add(W!($i));                                        
                $b = rol32!($b, 30);
            };
        }

        let mut a = digest[0];
        let mut b = digest[1];
        let mut c = digest[2];
        let mut d = digest[3];
        let mut e = digest[4];
        
        step00_19!(0, a, b, c, d, e);
        step00_19!(1, e, a, b, c, d);
        step00_19!(2, d, e, a, b, c);
        step00_19!(3, c, d, e, a, b);
        step00_19!(4, b, c, d, e, a);
        step00_19!(5, a, b, c, d, e);
        step00_19!(6, e, a, b, c, d);
        step00_19!(7, d, e, a, b, c);
        step00_19!(8, c, d, e, a, b);
        step00_19!(9, b, c, d, e, a);
        step00_19!(10, a, b, c, d, e);
        step00_19!(11, e, a, b, c, d);
        step00_19!(12, d, e, a, b, c);
        step00_19!(13, c, d, e, a, b);
        step00_19!(14, b, c, d, e, a);
        step00_19!(15, a, b, c, d, e);
        step00_19!(16, e, a, b, c, d);
        step00_19!(17, d, e, a, b, c);
        step00_19!(18, c, d, e, a, b);
        step00_19!(19, b, c, d, e, a);

        step20_39!(20, a, b, c, d, e);
        step20_39!(21, e, a, b, c, d);
        step20_39!(22, d, e, a, b, c);
        step20_39!(23, c, d, e, a, b);
        step20_39!(24, b, c, d, e, a);
        step20_39!(25, a, b, c, d, e);
        step20_39!(26, e, a, b, c, d);
        step20_39!(27, d, e, a, b, c);
        step20_39!(28, c, d, e, a, b);
        step20_39!(29, b, c, d, e, a);
        step20_39!(30, a, b, c, d, e);
        step20_39!(31, e, a, b, c, d);
        step20_39!(32, d, e, a, b, c);
        step20_39!(33, c, d, e, a, b);
        step20_39!(34, b, c, d, e, a);
        step20_39!(35, a, b, c, d, e);
        step20_39!(36, e, a, b, c, d);
        step20_39!(37, d, e, a, b, c);
        step20_39!(38, c, d, e, a, b);
        step20_39!(39, b, c, d, e, a);

        step40_59!(40, a, b, c, d, e);
        step40_59!(41, e, a, b, c, d);
        step40_59!(42, d, e, a, b, c);
        step40_59!(43, c, d, e, a, b);
        step40_59!(44, b, c, d, e, a);
        step40_59!(45, a, b, c, d, e);
        step40_59!(46, e, a, b, c, d);
        step40_59!(47, d, e, a, b, c);
        step40_59!(48, c, d, e, a, b);
        step40_59!(49, b, c, d, e, a);
        step40_59!(50, a, b, c, d, e);
        step40_59!(51, e, a, b, c, d);
        step40_59!(52, d, e, a, b, c);
        step40_59!(53, c, d, e, a, b);
        step40_59!(54, b, c, d, e, a);
        step40_59!(55, a, b, c, d, e);
        step40_59!(56, e, a, b, c, d);
        step40_59!(57, d, e, a, b, c);
        step40_59!(58, c, d, e, a, b);
        step40_59!(59, b, c, d, e, a);

        step60_79!(60, a, b, c, d, e);
        step60_79!(61, e, a, b, c, d);
        step60_79!(62, d, e, a, b, c);
        step60_79!(63, c, d, e, a, b);
        step60_79!(64, b, c, d, e, a);
        step60_79!(65, a, b, c, d, e);
        step60_79!(66, e, a, b, c, d);
        step60_79!(67, d, e, a, b, c);
        step60_79!(68, c, d, e, a, b);
        step60_79!(69, b, c, d, e, a);
        step60_79!(70, a, b, c, d, e);
        step60_79!(71, e, a, b, c, d);
        step60_79!(72, d, e, a, b, c);
        step60_79!(73, c, d, e, a, b);
        step60_79!(74, b, c, d, e, a);
        step60_79!(75, a, b, c, d, e);
        step60_79!(76, e, a, b, c, d);
        step60_79!(77, d, e, a, b, c);
        step60_79!(78, c, d, e, a, b);
        step60_79!(79, b, c, d, e, a);

        digest[0] = digest[0].wrapping_add(a);
        digest[1] = digest[1].wrapping_add(b);
        digest[2] = digest[2].wrapping_add(c);
        digest[3] = digest[3].wrapping_add(d);
        digest[4] = digest[4].wrapping_add(e);
    }
}